package server;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringReader;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Random;
import java.util.Scanner;

import javax.swing.Timer;

import parsing.CritterParser;
import parsing.InvalidSyntaxException;
import parsing.productions.Program;
import parsing.productions.Rule;
import util.SafeRingBuffer;
import world.Critter;
import world.CritterWorld;
import world.Hex;
import world.LoadState;
import world.Location;
import world.Species;

public class SimulationServer implements AdminServer {
	
	private ServerWrapper viewStub;
	private PlayerServerWrapper playerStub;
	
	private CritterWorld world;
	private String worldLoaded;
	private SafeRingBuffer<Thread> jobs;
	private Thread[] dispatchThreads;
	private final Object dispatchMonitor = new Object();//just for monitor purposes
	private final Object fileMonitor = new Object();//just for monitor purposes
	private Timer worldTimer;
	private long simRate;//steps per minute
	private boolean playing;
	private boolean uploadsOn;
	private boolean downloadsOn;
	
	private HashMap<String, String> players;//name, password
	private HashMap<String, String> admins;//name, password
	
	private HashMap<String, String> playerReqs;//name, password
	private HashMap<String, String> adminReqs;//name, password
	private HashMap<String, Thread> playerReqThreads;//name, thread
	private HashMap<String, Thread> adminReqThreads;//name, thread
	
	public static final String PENDING_REQUEST = "Request Pending";
	public static final String ACCEPTED_REQUEST = "Request Accepted";
	public static final String REJECTED_REQUEST = "Request Rejected";
	public static final String THREAD_IN_QUEUE = "In Queue";
	public static final String THREAD_OUT_OF_QUEUE = "Out of Queue";
	public static final String THREAD_DONE = "Done Executing";
	
	public SimulationServer(String initAdminName, String initAdminPass, int numDispatchThreads){
		try{
			world = new CritterWorld("constants.txt");
			jobs = new SafeRingBuffer<Thread>(20);
		} catch (Exception e){
			System.err.println("Server Instantiation Error:\n" + e.getMessage());
			e.printStackTrace();
			System.exit(0);
		}
		viewStub = new ServerWrapper(this);
		playerStub = new PlayerServerWrapper(this);
		
		simRate = 60;
		playing = false;
		uploadsOn = true;
		downloadsOn = true;
		worldTimer = new Timer((int)(1.0/simRate * 60000), new TimerListener());
		
		players = new HashMap<String, String>();
		admins = new HashMap<String, String>();
		admins.put(initAdminName, initAdminPass);
		
		playerReqs = new HashMap<String, String>();
		adminReqs = new HashMap<String, String>();
		playerReqThreads = new HashMap<String, Thread>();
		adminReqThreads = new HashMap<String, Thread>();
		
		dispatchThreads = new Thread[numDispatchThreads];
		for(int i = 0; i < dispatchThreads.length; i++){
			dispatchThreads[i] = new DispatchThread();
			dispatchThreads[i].setName("Server Dispatch " + i);
		}
		for(Thread t: dispatchThreads)
			t.start();
	}
	
	/**
	 * Initialized this server and exports it to be remote
	 * Requires: args contains {initialAdminUserName, initialAdminPassword, (int) numberOfDispatchThreads}
	 * @param args the initial admin's credentials and the number of dispatch threads this server will run
	 */
	public static void main(String[] args){
		try {
		    SimulationServer baseServer = new SimulationServer(args[0], args[1], Integer.parseInt(args[2]));
		    Server viewStub = (Server) UnicastRemoteObject.exportObject(baseServer.viewStub, 0);
		    UnicastRemoteObject.exportObject(baseServer.playerStub, 10);
		    UnicastRemoteObject.exportObject(baseServer, 20);

		    // Bind the remote object's stub in the registry
		    Registry registry = LocateRegistry.getRegistry();
		    registry.bind("server", viewStub);

		    System.err.println("Server ready");
		} catch (Exception e) {
		    System.err.println("Server exception: " + e.toString());
		    e.printStackTrace();
		}
	}
	
	private void addCurrentThread() throws RemoteException{
		Thread current = Thread.currentThread();
		if(!jobs.offer(current))
			throw new RemoteException("Server too busy. Try again later.");
		synchronized(current){
			current.setName(THREAD_IN_QUEUE);
			try {
				synchronized(dispatchMonitor){dispatchMonitor.notifyAll();}
				while(current.getName().equals(THREAD_IN_QUEUE))
					current.wait();
			} catch (InterruptedException e) {
				releaseCurrentThread();
			}
		}
	}
	
	private void releaseCurrentThread(){
		Thread current = Thread.currentThread();
		synchronized(current){
			current.setName(THREAD_DONE);
			current.notifyAll();
		}
	}

	@Override
	public String uploadCritter(final String program) throws RemoteException {
		if(!uploadsOn)
			throw new RemoteException("Critter uploads are disabled");
		addCurrentThread();
		
		Hex h = world.getRandomEmptyHex();
		Random rnd = new Random();
		Critter newCritter;
		try {
			newCritter = new Critter(world, h, rnd.nextInt(6), program);
		} catch (IllegalArgumentException e) {
			releaseCurrentThread();
			throw new RemoteException(e.getMessage());
		} catch (InvalidSyntaxException e) {
			releaseCurrentThread();
			throw new RemoteException(e.getMessage());
		} catch (IOException e) {
			releaseCurrentThread();
			throw new RemoteException(e.getMessage());
		}
		String fileName = "critter" + newCritter.CID + ".txt";
		try {
			synchronized (fileMonitor) {
				PrintWriter writer = new PrintWriter("critters/" + fileName);
				writer.print(program);
				writer.close();
			}
			releaseCurrentThread();
			return fileName;
		} catch (FileNotFoundException e) {
			releaseCurrentThread();
			throw new RemoteException("Critter added, but unable to save file");
		}
	}
	
	/**
	 * Upload many instances of one type of critter to the server
	 * @param program The filename of the critter definition file.
	 * @param numCrits the number of instances to make
	 * @throws RemoteException
	 */
	public void uploadManyCritters(final String program, int numCrits) throws RemoteException {
		if(!uploadsOn)
			throw new RemoteException("Critter uploads are disabled");
		addCurrentThread();

		Scanner critScanner = new Scanner(program);
		int memSize = 0, defense = 0, offense = 0;
		try {
			int values = 0;
			String lineStart;
			Random rnd = new Random();
			while(values < 6){
				lineStart = critScanner.next();
				if("memsize:".equalsIgnoreCase(lineStart)){
					memSize = Integer.parseInt(critScanner.next());
					values++;
				}
				else if("defense:".equalsIgnoreCase(lineStart)){
					String next = critScanner.next();
					if ("random".equals(next))
						defense = rnd.nextInt(world.CRITTER_MAX_DEF);
					else 
						defense = Integer.parseInt(next);
					values++;
				}
				else if("offense:".equalsIgnoreCase(lineStart)){
					String next = critScanner.next();
					if ("random".equals(next)) 
						offense = rnd.nextInt(world.CRITTER_MAX_OFF);
					else 
						offense = Integer.parseInt(next);
					values++;
				}
				else if("size:".equalsIgnoreCase(lineStart)){
					critScanner.next();
					values++;
				}
				else if("energy:".equalsIgnoreCase(lineStart)){
					critScanner.next();
					values++;
				}
				else if("appearance:".equalsIgnoreCase(lineStart)){
					critScanner.next();
					values++;
				}
				if(values < 6)
					critScanner.nextLine();
			}
		} catch (NumberFormatException e) {
			releaseCurrentThread();
			throw new RemoteException("Criter file was not formatted properly.");
		} catch (NoSuchElementException e){
			releaseCurrentThread();
			throw new RemoteException("Criter file did not specify all critter atributes.");
		}
		CritterParser cp = new CritterParser();
		critScanner.nextLine();
		StringBuffer sb = new StringBuffer();
		while(critScanner.hasNextLine())
			sb.append(critScanner.nextLine() + "\n");
		try {
			Program p = cp.parse(new StringReader(sb.toString()));
			Species s = world.getSpecies(offense, defense, memSize, p, null);
			for (int i = 0; i < numCrits; i++){
				new Critter(world, world.getRandomEmptyHex(), s);
			}
		} catch (IllegalArgumentException e) {
			releaseCurrentThread();
			throw new RemoteException(e.getMessage());
		} catch (InvalidSyntaxException e) {
			releaseCurrentThread();
			throw new RemoteException(e.getMessage());
		}
		releaseCurrentThread();
	}

	@Override
	public int maxColumn() throws RemoteException {
		return world.MAX_COLUMN;
	}

	@Override
	public int maxRow() throws RemoteException {
		return world.MAX_ROW;
	}

	@Override
	public Cell[] getSubsection(int llCol, int llRow, int numCols, int numRows) throws RemoteException {
		addCurrentThread();
		
		LinkedList<Cell> nonEmptyCells = new LinkedList<Cell>();
		synchronized(world) {
			for (int c = llCol; c < llCol + numCols; c++) {
				for (int r = llRow + (c - llCol)/2; r < llRow + (c - llCol)/2 + numRows; r++) {
					Hex curHex = world.getHex(new Location(world, r, c));
					if (curHex != null && (curHex.isOccupied() || curHex.getEnergy() > 0))
						nonEmptyCells.addFirst(hexToCell(curHex));
				}
			}
		}
		Cell[] rtn = new Cell[nonEmptyCells.size()];
		rtn = nonEmptyCells.toArray(rtn);
		releaseCurrentThread();
		return rtn;
	}

	private Cell hexToCell(Hex h) {
		Location loc = h.getLocation();
		int critId; int speciesId; int critSize; int critDirection; int critAppearance;
		if (h.hasCritter()) {
			Critter crit = h.getCritter();
			critId = crit.CID;
			speciesId = crit.SID;
			critSize = crit.getMemValue(3);
			critDirection = crit.getDirection();
			critAppearance = crit.getMemValue(5);
		} else {
			critId = speciesId = critSize = critDirection = critAppearance = -1;
		}
		Cell rtn = new Cell(loc.getRow(), loc.getCol(), 
				h.hasCritter(), h.hasPlant(), 
				h.isRock(), h.getFood() > 0,
				critId, speciesId, critSize,
				critDirection, critAppearance);
		return rtn;
	}

	@Override
	public boolean isRunning() throws RemoteException {
		return playing;
	}

	@Override
	public long getSimRate() throws RemoteException {
		return simRate;
	}

	@Override
	public int stepsCount() throws RemoteException {
		return world.getTime();
	}

	@Override
	public int numCritters() throws RemoteException {
		return world.getNumCritters();
	}

	@Override
	public int numPlants() throws RemoteException {
		return world.getNumPlants();
	}

	@Override
	public String getCritterProgram(int id) throws RemoteException {
		if(!downloadsOn)
			return null;
		addCurrentThread();
		
		Critter c = world.getCritter(id);
		if(c == null){
			releaseCurrentThread();
			return null;
		}
		Program p = c.getGenome();
		if(p == null){
			releaseCurrentThread();
			return null;
		}
		StringBuffer sb = new StringBuffer();
		p.prettyPrint(sb);
		releaseCurrentThread();
		return sb.toString();
	}

	@Override
	public int[] getCritterMemory(int id) throws RemoteException {
		addCurrentThread();
		
		Critter c = world.getCritter(id);
		if(c == null){
			releaseCurrentThread();
			return null;
		}
		int[] mem = new int[7];
		for(int i = 0; i < 7; i++)
			mem[i] = c.getMemValue(i);
		releaseCurrentThread();
		return mem;
	}

	@Override
	public String getCritterCurrentRule(int id) throws RemoteException {
		addCurrentThread();
		
		Critter c = world.getCritter(id);
		if(c == null){
			releaseCurrentThread();
			return null;
		}
		Rule r = c.getLastRule();
		if(r == null){
			releaseCurrentThread();
			return null;
		}
		StringBuffer sb = new StringBuffer();
		r.prettyPrint(sb);
		releaseCurrentThread();
		return sb.toString();
	}

	@Override
	public Action getCritterAction(int id) throws RemoteException {
		addCurrentThread();
		
		Critter crit = world.getCritter(id);
		if(crit == null){
			releaseCurrentThread();
			return null;
		}
		Action rtn;
		synchronized(crit) {
			switch(crit.getNextAction()) {
			case Critter.REST: rtn = Action.WAIT; break;
			case Critter.FORWARD: rtn = Action.FORWARD; break;
			case Critter.BACKWARD: rtn = Action.BACKWARD; break;
			case Critter.LEFT: rtn = Action.LEFT; break;
			case Critter.RIGHT: rtn = Action.RIGHT; break;
			case Critter.EAT: rtn = Action.EAT; break;
			case Critter.ATTACK: rtn = Action.ATTACK; break;
			case Critter.GROW: rtn = Action.GROW; break;
			case Critter.BUD: rtn = Action.BUD; break;
			case Critter.MATE: rtn = Action.MATE; break;
			default: rtn = null; break;
			}
			releaseCurrentThread();
			return rtn;
		}
	}

	@Override
	public String requestUserAcc(String user, String pw) throws RemoteException {
		if (user == null || pw == null)
			throw new RemoteException("Cannot create a user with a null name or null password.");
		addCurrentThread();
		
		synchronized(playerReqs){
			releaseCurrentThread();
			playerReqs.put(user, pw);
		}
		return addPlayerReqThread(user);
	}
	
	private String addPlayerReqThread(String user) throws RemoteException{
		Thread current = Thread.currentThread();
		synchronized(playerReqThreads){
			playerReqThreads.put(user, current);
		}
		current.setName(PENDING_REQUEST);
		synchronized(current){
			try {
				while(current.getName().equals(PENDING_REQUEST))
					current.wait();
			} catch (InterruptedException e) {
				releaseCurrentThread();
				throw new RemoteException("Server Error: Request interrupted");
			}
		}
		if(current.getName().equals(REJECTED_REQUEST))
			return "Player access denied";
		else
			return "Player access approved";
	}

	@Override
	public String requestAdminAcc(String user, String pw) throws RemoteException {
		if (user == null || pw == null)
			throw new RemoteException("Cannot create an admin with a null name or null password.");
		addCurrentThread();

		synchronized(adminReqs){
			releaseCurrentThread();
			adminReqs.put(user, pw);
		}
		return addAdminReqThread(user);
	}

	private String addAdminReqThread(String user) throws RemoteException{
		Thread current = Thread.currentThread();
		synchronized(adminReqThreads){
			adminReqThreads.put(user, current);
		}
		current.setName(PENDING_REQUEST);
		synchronized(current){
			try {
				while(current.getName().equals(PENDING_REQUEST))
					current.wait();
			} catch (InterruptedException e) {
				releaseCurrentThread();
				throw new RemoteException("Server Error: Request interrupted");
			}
		}
		if(current.getName().equals(REJECTED_REQUEST))
			return "Admin access denied";
		else
			return "Admin access approved";
	}

	@Override
	public PlayerServer getPlayerServer(String user, String pw) throws RemoteException{
		addCurrentThread();
		
		String uPass;
		synchronized(players){
			uPass = players.get(user);
		}
		releaseCurrentThread();
		if(uPass == null || !uPass.equals(pw))
			return null;
		return playerStub;
	}

	@Override
	public AdminServer getAdminServer(String user, String pw) throws RemoteException{
		addCurrentThread();
		
		String aPass;
		synchronized(admins){
			aPass = admins.get(user);
		}
		releaseCurrentThread();
		if(aPass == null || !aPass.equals(pw))
			return null;
		return this;
	}

	@Override
	public int[] getSpeciesAttributes(int species_id) throws RemoteException {
		addCurrentThread();
		
		Species s = world.getSpecies(species_id);
		int[] attr = new int[3];
		attr[0] = s.getMemLength();
		attr[1] = s.getDefense();
		attr[2] = s.getOffense();
		releaseCurrentThread();
		return attr;
	}

	@Override
	public String getSpeciesProgram(int species_id) throws RemoteException {
		if(!downloadsOn)
			return null;
		addCurrentThread();
		
		Species s = world.getSpecies(species_id);
		Program p = s.getProgram();
		StringBuffer sb = new StringBuffer();
		p.prettyPrint(sb);
		releaseCurrentThread();
		return sb.toString();
	}

	@Override
	public int[] getLineage(int species_id) throws RemoteException {
		addCurrentThread();
		
		List<Integer> linList = world.getSpecies(species_id).lineage();
		int[] lineage = new int[linList.size()];
		for(int i = 0; i < lineage.length; i++)
			lineage[i] = linList.get(i);
		releaseCurrentThread();
		return lineage;
	}

	@Override
	public void loadWorld(String world) throws RemoteException {
		addCurrentThread();
		
		synchronized(world){
			LoadState state = new LoadState();
			try {
				synchronized (fileMonitor) {
					this.world = new CritterWorld("constants.txt", world, state);
					if(!state.successful()){
						releaseCurrentThread();
						throw new RemoteException("The world was sucessfully loaded but not every component was added" +
				  				"\nbecause one or more was in an illegal location.");
					}
				}
			} catch (InvalidSyntaxException e) {
				releaseCurrentThread();
				throw new RemoteException(e.getMessage());
			} catch (IllegalArgumentException e) {
				releaseCurrentThread();
				throw new RemoteException(e.getMessage());
			} catch (IOException e) {
				releaseCurrentThread();
				throw new RemoteException(e.getMessage());
			}
		}
		worldLoaded = world;
		releaseCurrentThread();
	}

	@Override
	public void simStep() throws RemoteException {
		addCurrentThread();

		synchronized(this){
			if(playing){
				playing = false;
				worldTimer.stop();
			}
		}
		world.incrementTimestep();
		releaseCurrentThread();
	}

	@Override
	public void startSim() throws RemoteException {
		synchronized(this){
			if(!playing){
				playing = true;
				worldTimer.start();
			}
		}
	}

	@Override
	public void pauseSim() throws RemoteException {
		synchronized(this){
			if(playing){
				playing = false;
				worldTimer.stop();
			}
		}
	}

	@Override
	public void resetSim() throws RemoteException {
		addCurrentThread();
		
		try {
			if(worldLoaded == null)
				world = new CritterWorld("constants.txt");
			else{
				LoadState state = new LoadState();
				synchronized (fileMonitor) {
					world = new CritterWorld("constants.txt", worldLoaded,
							state);
				}
				if(!state.successful()){
					releaseCurrentThread();
					throw new RemoteException("The world was sucessfully loaded but not every component was added" +
							  				"\nbecause one or more was in an illegal location.");
				}
			}
		} catch (InvalidSyntaxException e) {
			releaseCurrentThread();
			throw new RemoteException(e.getMessage());
		} catch (IllegalArgumentException e) {
			releaseCurrentThread();
			throw new RemoteException(e.getMessage());
		} catch (IOException e) {
			releaseCurrentThread();
			throw new RemoteException(e.getMessage());
		}
		releaseCurrentThread();
	}

	@Override
	public void setSimRate(long rate) throws RemoteException {
		synchronized(this){
			simRate = rate;
			worldTimer.setDelay((int)(1.0/simRate * 60000));
		}
	}

	@Override
	public void killAll() throws RemoteException {
		addCurrentThread();
		
		world.removeAllCritters();
		releaseCurrentThread();
	}

	@Override
	public void kill(int id) throws RemoteException {
		addCurrentThread();
		
		Critter crit = world.getCritter(id);
		if (crit == null){
			releaseCurrentThread();
			throw new RemoteException("The given critter Id was not valid.");
		}
		synchronized(crit) {
			Hex h = crit.getHex();
			h.removeCritter();
			h.putFood(world.FOOD_PER_SIZE * crit.getMemValue(3));
			world.removeCritter(crit);
		}
		releaseCurrentThread();
	}

	@Override
	public void control(int id, Action a) throws RemoteException {
		addCurrentThread();
		
		Critter crit = world.getCritter(id);
		if (crit == null){
			releaseCurrentThread();
			throw new RemoteException("The given critter Id was not valid.");
		}
		synchronized(crit) {
			switch(a) {
			case WAIT: crit.setNextAction(Critter.REST); break;
			case FORWARD: crit.setNextAction(Critter.FORWARD); break;
			case BACKWARD: crit.setNextAction(Critter.BACKWARD); break;
			case LEFT: crit.setNextAction(Critter.LEFT); break;
			case RIGHT: crit.setNextAction(Critter.RIGHT); break;
			case EAT: crit.setNextAction(Critter.EAT); break;
			case ATTACK: crit.setNextAction(Critter.ATTACK); break;
			case GROW: crit.setNextAction(Critter.GROW); break;
			case BUD: crit.setNextAction(Critter.BUD); break;
			case MATE: crit.setNextAction(Critter.MATE); break;
			default: crit.setNextAction(Critter.PROGRAM);
			}
		}
		releaseCurrentThread();
	}

	@Override
	public String[] listCritterFiles() throws RemoteException {
		addCurrentThread();
		
		File[] files;
		synchronized (fileMonitor) {
			File directory = new File("critters/");
			files = directory.listFiles();
		}
		String[] fileNames = new String[files.length];
		for(int i = 0; i < files.length; i++)
			fileNames[i] = files[i].getName();
		releaseCurrentThread();
		return fileNames;
	}

	@Override
	public boolean uploadsOn() throws RemoteException {
		return uploadsOn;
	}

	@Override
	public void setCritterUploads(boolean on) throws RemoteException {
		synchronized(this){
			uploadsOn = on;
		}
	}

	@Override
	public boolean downloadsOn() throws RemoteException {
		return downloadsOn;
	}

	@Override
	public void setCritterDownloads(boolean on) throws RemoteException {
		synchronized(this){
			downloadsOn = on;
		}
	}

	@Override
	public String[] getPlayerList() throws RemoteException {
		addCurrentThread();
		
		Collection<String> names;
		synchronized(players){
			names = players.keySet();
		}
		String[] nameArr = names.toArray(new String[0]);
		releaseCurrentThread();
		return nameArr;
	}

	@Override
	public String[] getPlayerRequests() throws RemoteException {
		addCurrentThread();
		
		Collection<String> reqs;
		synchronized(playerReqs){
			reqs = playerReqs.values();
		}
		String[] reqArr = reqs.toArray(new String[0]);
		releaseCurrentThread();
		return reqArr;
	}

	@Override
	public void addPlayer(String name) throws RemoteException {
		addCurrentThread();
		
		Thread pendingRequest;
		synchronized(playerReqThreads) {
			 pendingRequest = playerReqThreads.remove(name);
		}
		if (pendingRequest == null){
			releaseCurrentThread();
			throw new RemoteException("No player with this name has requested access.");
		}
		String passWord;
		synchronized(playerReqs) {
			passWord = playerReqs.remove(name);
		}
		if (passWord == null){
			releaseCurrentThread();
			throw new RemoteException("Error: could not find player password");
		}
		synchronized(players) {
			players.put(name, passWord);
		}
		synchronized(pendingRequest) {
			pendingRequest.setName(ACCEPTED_REQUEST);
			pendingRequest.notifyAll();
		}
		releaseCurrentThread();
	}

	@Override
	public void rejectPlayer(String name) throws RemoteException {
		addCurrentThread();
		
		Thread pendingRequest;
		synchronized(playerReqThreads) {
			 pendingRequest = playerReqThreads.remove(name);
		}
		if (pendingRequest == null){
			releaseCurrentThread();
			throw new RemoteException("No player with this name has requested access.");
		}
		String passWord;
		synchronized(playerReqs) {
			passWord = playerReqs.remove(name);
		}
		if (passWord == null){
			releaseCurrentThread();
			throw new RemoteException("Error: could not find player password");
		}
		synchronized(pendingRequest) {
			pendingRequest.setName(REJECTED_REQUEST);
			pendingRequest.notifyAll();
		}
		releaseCurrentThread();
	}

	@Override
	public void removePlayer(String name) throws RemoteException {
		addCurrentThread();
		
		synchronized(players) {
			players.remove(name);
		}
		releaseCurrentThread();
	}

	@Override
	public String[] getAdminList() throws RemoteException {
		addCurrentThread();
		
		Collection<String> names;
		synchronized(admins){
			names = admins.keySet();
		}
		String[] nameArr = names.toArray(new String[0]);
		releaseCurrentThread();
		return nameArr;
	}

	@Override
	public String[] getAdminRequests() throws RemoteException {
		addCurrentThread();
		
		Collection<String> reqs;
		synchronized(adminReqs){
			reqs = adminReqs.values();
		}
		String[] reqArr = reqs.toArray(new String[0]);
		releaseCurrentThread();
		return reqArr;
	}

	@Override
	public void addAdmin(String name) throws RemoteException {
		addCurrentThread();
		
		Thread pendingRequest;
		synchronized(adminReqThreads) {
			pendingRequest = adminReqThreads.remove(name);
		}
		if (pendingRequest == null){
			releaseCurrentThread(); 
			throw new RemoteException("No admin with this name has requested access.");
		}
		String passWord;
		synchronized(adminReqs) {
			passWord = adminReqs.remove(name);
		}
		if (passWord == null){
			releaseCurrentThread();
			throw new RemoteException("Error: could not find admin password");
		}
		synchronized(admins) {
			admins.put(name, passWord);
		}
		synchronized(pendingRequest) {
			pendingRequest.setName(ACCEPTED_REQUEST);
			pendingRequest.notifyAll();
		}
		releaseCurrentThread();
	}

	@Override
	public void rejectAdmin(String name) throws RemoteException {
		addCurrentThread();
		
		Thread pendingRequest;
		synchronized(adminReqThreads) {
			pendingRequest = adminReqThreads.remove(name);
		}
		if (pendingRequest == null){
			releaseCurrentThread();
			throw new RemoteException("No admin with this name has requested access.");
		}
		String passWord;
		synchronized(adminReqs) {
			passWord = adminReqs.remove(name);
		}
		if (passWord == null){
			releaseCurrentThread();
			throw new RemoteException("Error: could not find admin password");
		}
		synchronized(pendingRequest) {
			pendingRequest.setName(REJECTED_REQUEST);
			pendingRequest.notifyAll();
		}
		releaseCurrentThread();
	}

	@Override
	public void removeAdmin(String name) throws RemoteException {
		addCurrentThread();

		synchronized(admins) {
			admins.remove(name);
		}
		releaseCurrentThread();
	}
	
	private class DispatchThread extends Thread{
		public void run(){
			while(true){
				try {
					if(!jobs.isEmpty()){
						Thread t = jobs.poll();
						synchronized(t){
							t.setName(THREAD_OUT_OF_QUEUE);
							t.notifyAll();
							while(t.getName().equals(THREAD_OUT_OF_QUEUE))
								t.wait();
						}
					} else
						synchronized(dispatchMonitor){dispatchMonitor.wait();}
				} catch (InterruptedException e) {
					System.err.println(Thread.currentThread().getName() + " Interrupted");
					e.printStackTrace();
				}
			}
		}
	}
	
	private class TimerListener implements ActionListener {
		public void actionPerformed(ActionEvent e) {
			try {
				addCurrentThread();
				
				world.incrementTimestep();
				releaseCurrentThread();
			} catch (RemoteException e1) {
				System.err.println("World increment thread: " + e1.getMessage());
			}
		}
	}

}
